#include "DEHT.h"
#include "misc.h"

			 
DEHT *create_empty_DEHT(const char *prefix,/*add .key and .data to open two files return NULL if fail creation*/
                        hashKeyIntoTableFunctionPtr hashfun, 
						hashKeyforEfficientComparisonFunctionPtr validfun,
                        const char *dictName,   /*e.g. MD5\0 */ 
                        int numEntriesInHashTable,
						int nPairsPerBlock,
						int nBytesPerKey){ /*optimization preferences*/

	int i, status;
	int block_size_in_bytes, pair_size_in_bytes;
	DEHT* ret;
	FILE *keyFP, *dataFP;
	char key_filename[MAX_INTPUT_SIZE];
	char data_filename[MAX_INTPUT_SIZE];
	char seed_filename[MAX_INTPUT_SIZE];
	DEHT_DISK_PTR temp = 0;
	unsigned char temp_char = '#';

	/*  check input arguments */
	if (!prefix || !hashfun || !validfun || !dictName) {
		return NULL;
	}

	if (numEntriesInHashTable <= 0 || nPairsPerBlock <= 0 || nBytesPerKey <= 0) {
		return NULL;
	}

	/* calculate block size */
	block_size_in_bytes = (nBytesPerKey + sizeof(DEHT_DISK_PTR)) * nPairsPerBlock +
			sizeof(DEHT_DISK_PTR);
	pair_size_in_bytes = nBytesPerKey + sizeof(DEHT_DISK_PTR);

	/* produce file names */
	strcpy(key_filename, prefix);
	strcat(key_filename, ".key");
	strcpy(data_filename, prefix);
	strcat(data_filename, ".data");
	strcpy(seed_filename, prefix);
	strcat(seed_filename, ".seed");

	/* check if files exsist */
	if ((keyFP = fopen(key_filename, "rb"))) {
		fprintf(stderr, "Error: File %s already exists\n", key_filename);
		fclose(keyFP);
		return NULL;
	}
	if ((dataFP = fopen(data_filename, "rb"))) {
		fprintf(stderr, "Error: File %s already exists\n", data_filename);
		fclose(dataFP);
		return NULL;
	}

	/* open file streams */
	keyFP = fopen(key_filename, "w+b");
	if (!keyFP) {
		perror(key_filename);
		return NULL;
	}

	dataFP = fopen(data_filename, "w+b");
	if (!dataFP) {
		perror(data_filename);
		fclose(keyFP);
		remove(key_filename);
		return NULL;
	}


	/* allocate memory */
	ret = (DEHT*) malloc(sizeof(DEHT));

	if (ret == NULL) {
		fprintf(stderr, "Error: Not enough memory\n");
		fclose(keyFP);
		fclose(dataFP);
		remove(key_filename);
		remove(data_filename);
		return NULL;
	}

	/* instilize structure */
	strcpy(ret->sDataFileName, data_filename);
	strcpy(ret->sKeyFileName, key_filename);
	strcpy(ret->sSeedFileName, seed_filename);
	ret->dataFP = dataFP;
	ret->keyFP = keyFP;

	ret->hashFunc = hashfun;
	ret->comparisonHashFunc = validfun;
	
	strcpy(ret->header.sHashName, dictName);
	ret->header.numEntriesInHashTable = numEntriesInHashTable;
	ret->header.nPairsPerBlock = nPairsPerBlock;
	ret->header.nBytesPerValidationKey = nBytesPerKey;
	
	ret->hashPointersForLastBlockImageInMemory = NULL;
	ret->hashTableOfPointersImageInMemory = NULL;
	
	ret->anLastBlockSize = NULL;
	ret->dataTempPointer = 0;
	ret->keyTempPointer = 0;
	ret->BlockSize = block_size_in_bytes;
	ret->PairSize = pair_size_in_bytes;

	ret->workBlock = (unsigned char*) calloc (block_size_in_bytes, sizeof(unsigned char));

	if (!ret->workBlock) {
		fprintf(stderr, "Error: not enough memory\n");
		free(ret);
		fclose(keyFP);
		fclose(dataFP);
		remove(key_filename);
		remove(data_filename);
		return NULL;
	}
	

	/* write header to the key file */
	status = fwrite(&(ret->header), sizeof(struct DEHTpreferences), 1, ret->keyFP);
	if (!status) {
		perror(ret->sKeyFileName);
		free(ret->workBlock);
		free(ret);
		fclose(keyFP);
		fclose(dataFP);
		remove(key_filename);
		remove(data_filename);
		return NULL;
	}


	/* write hash table of pointers to key file */
	for (i = 0; i < numEntriesInHashTable; i++) {
		status = fwrite(&temp, sizeof(DEHT_DISK_PTR), 1, ret->keyFP);
		if (!status) {
			perror(ret->sKeyFileName);
			free(ret->workBlock);
			free(ret);
			fclose(keyFP);
			fclose(dataFP);
			remove(key_filename);
			remove(data_filename);
			return NULL;
		}
	}

	/* write first byte of data file, to avoid having 0 offset */
	status = fwrite(&temp_char, sizeof(unsigned char), 1, ret->dataFP);
	if (!status) {
		perror(ret->sDataFileName);
		free(ret->workBlock);
		free(ret);
		fclose(keyFP);
		fclose(dataFP);
		remove(key_filename);
		remove(data_filename);
		return NULL;
	}

	return ret;
}

DEHT *load_DEHT_from_files(const char *prefix,
		hashKeyIntoTableFunctionPtr hashfun,
		hashKeyforEfficientComparisonFunctionPtr validfun) {
	
	int status;
	int block_size_in_bytes,pair_size_in_bytes;
	DEHT *ret;
	FILE *keyFP, *dataFP;
	char key_filename[MAX_INTPUT_SIZE];
	char data_filename[MAX_INTPUT_SIZE];
	char seed_filename[MAX_INTPUT_SIZE];

	/* prepare file names */
	strcpy(key_filename, prefix);
	strcpy(data_filename, prefix);
	strcpy(seed_filename, prefix);
	strcat(key_filename, ".key");
	strcat(data_filename, ".data");
	strcat(seed_filename, ".seed");

	/* check if file exist and open file streams */
	keyFP = fopen(key_filename, "r+b");
	if (!keyFP) {
		perror(key_filename);
		return NULL;
	}
	dataFP = fopen(data_filename, "r+b");
	if (!dataFP) {
		perror(data_filename);
		fclose(keyFP);
		return NULL;
	}

	/* allocate memory */
	ret = (DEHT*) malloc(sizeof(DEHT));
	if (!ret) {
		fprintf(stderr, "Error: Not enough memory\n");
		fclose(keyFP);
		fclose(dataFP);
		return NULL;
	}

	/* instilize new DEHT structure */
	strcpy(ret->sDataFileName, data_filename);
	strcpy(ret->sKeyFileName, key_filename);
	strcpy(ret->sSeedFileName, seed_filename);
	ret -> dataFP = dataFP;
	ret -> keyFP = keyFP;

	status = fread(&(ret->header), sizeof(struct DEHTpreferences), 1, ret->keyFP);
	if (!status) {
		perror(ret->sKeyFileName);
		free(ret);
		fclose(keyFP);
		fclose(dataFP);
		return NULL;
	}

	ret -> hashFunc = hashfun;
	ret -> comparisonHashFunc = validfun;

	ret->hashPointersForLastBlockImageInMemory = NULL;
	ret->hashTableOfPointersImageInMemory = NULL;
	
	ret->anLastBlockSize = NULL;
	ret->dataTempPointer = 0;
	ret->keyTempPointer = 0;
	
	block_size_in_bytes = (ret->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR)) *
			ret->header.nPairsPerBlock + sizeof(DEHT_DISK_PTR);
	pair_size_in_bytes = ret->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR);
	ret->BlockSize = block_size_in_bytes;
	ret->PairSize = pair_size_in_bytes;

	

	ret->workBlock = (unsigned char*) calloc (block_size_in_bytes, sizeof(unsigned char));
	if (!(ret->workBlock)) {
		fprintf(stderr, "Error: not enough memory\n");
		free(ret);
		fclose(keyFP);
		fclose(dataFP);
		return NULL;
	}

	return ret;
}


int insert_uniquely_DEHT(DEHT *ht, const unsigned char *key, int keyLength,
		const unsigned char *data, int dataLength) {

	int status_q;
	int status;
	unsigned char dummy_data[MAX_WORD];
	unsigned char data_check[MAX_WORD];
	unsigned char key_sig[MAX_KEY];
	unsigned char data_null_terminated[MAX_WORD + 1];
	unsigned char* block;
	int i;
	unsigned char zero = 0;
	int first_iter = 1;
	int block_data_size_in_bytes;
	int pair_size_in_bytes;
	DEHT_DISK_PTR header_size;
	DEHT_DISK_PTR block_ptr;
	DEHT_DISK_PTR data_ptr;
	DEHT_DISK_PTR key_ptr;

	pair_size_in_bytes = ht->PairSize;
	block_data_size_in_bytes = ht->BlockSize - sizeof(DEHT_DISK_PTR);
	header_size = sizeof(struct DEHTpreferences);

	pair_size_in_bytes = ht->PairSize;
	block_data_size_in_bytes = ht->BlockSize - sizeof(DEHT_DISK_PTR);

	/* exit if DEHT not found */
	if (!ht) {
		return DEHT_STATUS_FAIL;
	}
	/* make query in the ht */
	status_q = query_DEHT(ht, key, keyLength, dummy_data, MAX_WORD);
	if (status_q == DEHT_STATUS_FAIL) return DEHT_STATUS_FAIL;
	/* don't exist, just add */
	if (status_q == 0) 
		 return add_DEHT(ht, key, keyLength, data, dataLength);

	/* need to search for a key */
	block_ptr = ht->blockTempPointer;
	data_ptr = ht->dataTempPointer;
	key_ptr = ht->keyTempPointer;

	/* read relevant block to workBlock */
	block = ht->workBlock;
	status = fseek(ht->keyFP, block_ptr, SEEK_SET);
	if (status != 0) {
		perror(ht->sKeyFileName);
		return DEHT_STATUS_FAIL;
	}

	status = fread(block, sizeof(unsigned char), block_data_size_in_bytes, ht->keyFP);
	if (status != block_data_size_in_bytes) {
		perror(ht->sKeyFileName);
		return DEHT_STATUS_FAIL;
	}
	/*we already know the data we want to exchange*/
	ht->comparisonHashFunc(key, keyLength, key_sig);
	/* search blocks in the bucket */
	while(block_ptr) {
		for (i = 0; i < block_data_size_in_bytes; i += pair_size_in_bytes) {
			/* key found (could be false positive) */
			/* we make the assumption that data cannot exceed MAX_INPUT_SIZE in bytes */
			
			/* continue from place where query ended */
			if (first_iter){
				i = key_ptr - block_ptr;
				first_iter = 0;
			}
			/* check key in current block */
			if (!memcmp(block+i, key_sig, ht->header.nBytesPerValidationKey)) {
				/* get block pointer */
				memcpy(&data_ptr, block+i+ht->header.nBytesPerValidationKey, sizeof(DEHT_DISK_PTR));
				/* navigate in Data file and read */
				status = fseek(ht->dataFP, data_ptr, SEEK_SET);
				if (status != 0) {
					perror(ht->sDataFileName);
					return DEHT_STATUS_FAIL;
				}
				status = fread(data_null_terminated, sizeof(unsigned char), MAX_WORD + 1, ht->dataFP);
				if ((status != (MAX_INTPUT_SIZE - 1)) && ferror(ht->keyFP) ) {
					perror(ht->sDataFileName);
					return DEHT_STATUS_FAIL;
				}
				if (memcmp(data_check ,data_null_terminated,dataLength) != 0){
					key_ptr = block_ptr+i;
					break;
				}
			}
		}

		/* read next block in the list */
		status = fread(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
		if (!status) {
			perror(ht->sKeyFileName);
			return DEHT_STATUS_FAIL;
		}
		if (block_ptr) {
			status = fseek(ht->keyFP, block_ptr, SEEK_SET);
			if (status != 0) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
			status = fread(block, sizeof(unsigned char), block_data_size_in_bytes, ht->keyFP);
			if (status != block_data_size_in_bytes) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
		}
	}
	/* now we have key pointer */
		status = fseek(ht->keyFP, key_ptr, SEEK_SET);
		if (status != 0) { 
			perror(ht->sKeyFileName);
			return DEHT_STATUS_FAIL;
		}
		/* need to update data field */
		if (status_q < dataLength) {
			status = fseek(ht->dataFP, 0, SEEK_END);
			if (status != 0) { 
				perror(ht->sDataFileName);
				return DEHT_STATUS_FAIL;
			}
			data_ptr = ftell(ht->dataFP);
			status = fwrite(&data_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
			if (!status) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
		} 
		else {
			status = fseek(ht->dataFP, data_ptr, SEEK_SET);
			if (status != 0) { 
				perror(ht->sDataFileName);
				return DEHT_STATUS_FAIL;
			}
			status = fwrite(data, sizeof(unsigned char), dataLength, ht->dataFP);
			if (status != dataLength) {
				perror(ht->sDataFileName);
				return DEHT_STATUS_FAIL;
			}
		}
		status = fwrite(&zero, sizeof(unsigned char), 1, ht->dataFP);
		if (!status) {
			perror(ht->sDataFileName);
			return DEHT_STATUS_FAIL;
		}
		return DEHT_STATUS_NOT_NEEDED;
	}
/*
 * NOTE: due to DEHT design, it doesn't handle the empty word properly,
 * it adds the word correctly but when query is called, it return 0
 * which is the size of what was written and also DEHT_STATUS_NOT_NEEDED
 * so througout the project we make sure we never insert it in the first place
 */
int add_DEHT(DEHT *ht, const unsigned char *key, int keyLength,
		const unsigned char *data, int dataLength) {
	int entry;
	int status;
	int block_full = 0;
	unsigned char *block;
	unsigned char pair_bytes[MAX_WORD + MAX_KEY];
	unsigned char zero = 0;
	int pair_size_in_bytes;
	int block_size_in_bytes;
	DEHT_DISK_PTR next_ptr_offset;
	DEHT_DISK_PTR header_size;
	DEHT_DISK_PTR block_ptr;
	DEHT_DISK_PTR block_offset;
	DEHT_DISK_PTR prev_ptr;
	DEHT_DISK_PTR data_ptr;
	DEHT_DISK_PTR data_offset;

	pair_size_in_bytes = ht->PairSize;
	block_size_in_bytes = ht->BlockSize;
	next_ptr_offset = block_size_in_bytes - sizeof(DEHT_DISK_PTR);
	header_size = sizeof(struct DEHTpreferences);
	

	/* exit if DEHT not found */
	if (!ht) {
		return DEHT_STATUS_FAIL;
	}
	/* first add new entry to the end of data file */
	status = fseek(ht->dataFP, 0, SEEK_END);
	if (status != 0) { 
		perror(ht->sDataFileName);
		return DEHT_STATUS_FAIL;
	}
	data_offset = ftell(ht->dataFP);
	if (data_offset == -1) {
		perror(ht->sDataFileName);
		return DEHT_STATUS_FAIL;
	}
	if (dataLength > 0) {
		status = fwrite(data, sizeof(unsigned char), dataLength, ht->dataFP);
		if (status != dataLength) {
			perror(ht->sDataFileName);
			return DEHT_STATUS_FAIL;
		}
	}
	status = fwrite(&zero, sizeof(unsigned char), 1, ht->dataFP);
	if (!status) {
		perror(ht->sDataFileName);
		return DEHT_STATUS_FAIL;
	}

	/* calulate key to store */
	ht->comparisonHashFunc(key, keyLength, pair_bytes);
	*((DEHT_DISK_PTR*)(pair_bytes + ht->header.nBytesPerValidationKey)) = data_offset;

	/* calculate offset for key */
	entry = ht->hashFunc(key, keyLength, ht->header.numEntriesInHashTable);
	if (ht->hashPointersForLastBlockImageInMemory) {
		block_ptr = ht->hashPointersForLastBlockImageInMemory[entry];
		block_full = (ht->anLastBlockSize[entry] == ht->header.nPairsPerBlock - 1);
	} else {
		if (ht->hashTableOfPointersImageInMemory) {
			block_ptr = ht->hashTableOfPointersImageInMemory[entry];
		} else {
			status = fseek(ht->keyFP, header_size + entry * sizeof(DEHT_DISK_PTR), SEEK_SET);
			if (status != 0) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
			status = fread(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
			if (!status) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
		}

		/* find last block */
		prev_ptr = 0;
		while (block_ptr) {
			prev_ptr = block_ptr;
			status = fseek(ht->keyFP, block_ptr + next_ptr_offset, SEEK_SET);
			if (status != 0) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
			}
			status = fread(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
			if (!status) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
		}
		block_ptr = prev_ptr;
		/* check if last block is full */
		if (block_ptr) {
			status = fseek(ht->keyFP, block_ptr + next_ptr_offset - sizeof(DEHT_DISK_PTR) , SEEK_SET);
			if (status != 0) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
			}
			status = fread(&block_full, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
			if (!status) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
			status = fseek(ht->keyFP, -1 * next_ptr_offset, SEEK_CUR);
			if (status != 0) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
			}
		}
	}

	/* need to write a  first block or block is full */
	if (!block_ptr  || block_full) {
		prev_ptr = block_ptr;
		status = fseek(ht->keyFP, 0, SEEK_END);
		if (status != 0) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
		}
		block_ptr = ftell(ht->keyFP);

		/* update last block and it's size */
		if (ht->hashPointersForLastBlockImageInMemory) {
			ht->hashPointersForLastBlockImageInMemory[entry] = block_ptr;
			ht->anLastBlockSize[entry] = 0;
		}
		/* special case for */
		if (ht->header.nPairsPerBlock == 1){
			if (prev_ptr == 0){
				if (ht->hashTableOfPointersImageInMemory) {
					ht->hashTableOfPointersImageInMemory[entry] = block_ptr;
				} else {
					status = fseek(ht->keyFP, header_size + entry * sizeof(DEHT_DISK_PTR), SEEK_SET);
					if (status != 0) {
						perror(ht->sKeyFileName);
						return DEHT_STATUS_FAIL;
					}
					status = fwrite(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
					if (!status) {
						perror(ht->sKeyFileName);
						return DEHT_STATUS_FAIL;
					}
				}
			}
			else{
				status = fseek(ht->keyFP, prev_ptr + next_ptr_offset, SEEK_SET);
				if (status != 0) {
						perror(ht->sKeyFileName);
						return DEHT_STATUS_FAIL;
				}
				status = fwrite(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
				if (!status) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
				}
			}
		}
		else{
			/* block is full, update pointer to the next block */
			if (block_full) {
				status = fseek(ht->keyFP, prev_ptr + next_ptr_offset, SEEK_SET);
				if (status != 0) {
						perror(ht->sKeyFileName);
						return DEHT_STATUS_FAIL;
				}
				status = fwrite(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
				if (!status) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
				}
			}/* a first new block */ 
			else {
				if (ht->hashTableOfPointersImageInMemory) {
					ht->hashTableOfPointersImageInMemory[entry] = block_ptr;
				} else {
					status = fseek(ht->keyFP, header_size + entry * sizeof(DEHT_DISK_PTR), SEEK_SET);
					if (status != 0) {
						perror(ht->sKeyFileName);
						return DEHT_STATUS_FAIL;
					}
					status = fwrite(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
					if (!status) {
						perror(ht->sKeyFileName);
						return DEHT_STATUS_FAIL;
					}
				}
			}
		}
		/* check if we need fseek */
		if (block_full || !ht->hashTableOfPointersImageInMemory) {
			status = fseek(ht->keyFP,  block_ptr, SEEK_SET);
			if (status != 0) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
			}
		}
		
		status = fwrite(pair_bytes, sizeof(unsigned char), pair_size_in_bytes ,ht->keyFP);
		if (status != pair_size_in_bytes) {
			perror(ht->sKeyFileName);
			return DEHT_STATUS_FAIL;
		}

		/* update current work block */
		block = ht->workBlock;
		memset(block, 0, block_size_in_bytes);
		status = fwrite(block, sizeof(unsigned char), block_size_in_bytes - pair_size_in_bytes ,ht->keyFP);
		if (status != (block_size_in_bytes - pair_size_in_bytes)) {
			perror(ht->sKeyFileName);
			return DEHT_STATUS_FAIL;
		}
	}/* block is not full, add to last block */ 
	else {
		/* calculate offset in the block */
		if (ht->hashPointersForLastBlockImageInMemory) {
			(ht->anLastBlockSize[entry])++;
			block_offset = ht->anLastBlockSize[entry] * pair_size_in_bytes;
		} else {
			block = ht->workBlock;
			status = fseek(ht->keyFP, block_ptr, SEEK_SET);
			if (status != 0) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
			}
			status = fread(block, sizeof(unsigned char), block_size_in_bytes, ht->keyFP);
			if (status != block_size_in_bytes) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
			data_ptr = 1;
			for (block_offset = 0; data_ptr; block_offset += pair_size_in_bytes) {
				memcpy(&data_ptr, block+block_offset+ht->header.nBytesPerValidationKey, sizeof(DEHT_DISK_PTR));
			}
			block_offset -= pair_size_in_bytes;
		}

		/* write data */
		status = fseek(ht->keyFP, block_ptr + block_offset, SEEK_SET);
		if (status != 0) {
					perror(ht->sKeyFileName);
					return DEHT_STATUS_FAIL;
		}
		status = fwrite(pair_bytes, sizeof(unsigned char), pair_size_in_bytes, ht->keyFP);
		if (status != pair_size_in_bytes) {
			perror(ht->sKeyFileName);
			return DEHT_STATUS_FAIL;
		}

	}

	return DEHT_STATUS_SUCCESS;
}


int mult_query_DEHT ( DEHT *ht, const unsigned char *key, int keyLength, 
					unsigned char *data, int dataMaxAllowedLength,
					unsigned char **dataPointer, int dataPointerLength){
	int data_offset = 0;
	int status;
	unsigned char key_sig[MAX_KEY];
	unsigned char data_null_terminated[MAX_WORD + 1];
	unsigned char* block;
	int entry;
	int i;
	unsigned char zero = 0;
	int counter = 0;
	int data_size;
	int block_data_size_in_bytes;
	int pair_size_in_bytes;
	DEHT_DISK_PTR header_size;
	DEHT_DISK_PTR block_ptr;
	DEHT_DISK_PTR data_ptr;

	pair_size_in_bytes = ht->PairSize;
	block_data_size_in_bytes = ht->BlockSize - sizeof(DEHT_DISK_PTR);
	header_size = sizeof(struct DEHTpreferences);

	/* exit if DEHT not found */
	if (!ht) {
		return DEHT_STATUS_FAIL;
	}

	/* calculate stored key and adress in hash table */
	ht->comparisonHashFunc(key, keyLength, key_sig);
	entry = ht->hashFunc(key, keyLength, ht->header.numEntriesInHashTable);

	/* look for key */
	if (ht->hashTableOfPointersImageInMemory) {
		block_ptr = ht->hashTableOfPointersImageInMemory[entry];
	}
	else {
		status = fseek(ht->keyFP, header_size + entry * sizeof(DEHT_DISK_PTR), SEEK_SET);
		if (status != 0) {
		perror(ht->sKeyFileName);
		return DEHT_STATUS_FAIL;
		}
		status = fread(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
		if (!status) {
			perror(ht->sKeyFileName);
			return DEHT_STATUS_FAIL;
		}
	}
	/* block not found */
	if (!block_ptr)
		return DEHT_STATUS_NOT_NEEDED;

	/* read first block to workBlock */
	block = ht->workBlock;
	status = fseek(ht->keyFP, block_ptr, SEEK_SET);
	if (status != 0) {
		perror(ht->sKeyFileName);
		return DEHT_STATUS_FAIL;
	}

	status = fread(block, sizeof(unsigned char), block_data_size_in_bytes, ht->keyFP);
	if (status != block_data_size_in_bytes) {
		perror(ht->sKeyFileName);
		return DEHT_STATUS_FAIL;
	}

	/* search blocks in the bucket */
	while(block_ptr) {
		for (i = 0; i < block_data_size_in_bytes; i += pair_size_in_bytes) {
			/* key found (could be false positive) */
			/* we make the assumption that data cannot exceed MAX_INPUT_SIZE in bytes */
			
			/* check key in current block */
			if (!memcmp(block+i, key_sig, ht->header.nBytesPerValidationKey)) {
				/* get block pointer */
				memcpy(&data_ptr, block+i+ht->header.nBytesPerValidationKey, sizeof(DEHT_DISK_PTR));
				/* navigate in Data file and read */
				status = fseek(ht->dataFP, data_ptr, SEEK_SET);
				if (status != 0) {
					perror(ht->sDataFileName);
					return DEHT_STATUS_FAIL;
				}
				status = fread(data_null_terminated, sizeof(unsigned char), MAX_WORD + 1, ht->dataFP);
				if ((status != (MAX_INTPUT_SIZE - 1)) && ferror(ht->keyFP) ) {
					perror(ht->sDataFileName);
					return DEHT_STATUS_FAIL;
				}

				data_size = strlen((char*)data_null_terminated);
				data_offset+=data_size;
				if (data_offset > (dataMaxAllowedLength - 1)){

					memcpy(data, &zero, 1);
					*dataPointer = data;
					return counter;
				}
				memcpy(data, data_null_terminated, data_size);
				*dataPointer = data;
				data += data_size;
				dataPointer++;
				counter++;
				if (counter == 1){
				/* keep these values in case unique_insert  called query */
				ht->dataTempPointer = data_ptr;
				ht->blockTempPointer = block_ptr;
				ht->keyTempPointer = block_ptr+i+ht->header.nBytesPerValidationKey;
				}
				if (counter == dataPointerLength - 1){
					memcpy(data, &zero, 1);
					*dataPointer = data;
					return counter;
				}
			}
		}

		/* read next block in the list */
		status = fread(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
		if (!status) {
			perror(ht->sKeyFileName);
			return DEHT_STATUS_FAIL;
		}
		if (block_ptr) {
			status = fseek(ht->keyFP, block_ptr, SEEK_SET);
			if (status != 0) {
					perror(ht->sDataFileName);
					return DEHT_STATUS_FAIL;
			}
			status = fread(block, sizeof(unsigned char), block_data_size_in_bytes, ht->keyFP);
			if (status != block_data_size_in_bytes) {
				perror(ht->sKeyFileName);
				return DEHT_STATUS_FAIL;
			}
		}
	}

	memcpy(data, &zero, 1);
	*dataPointer = data;
	return counter;
}

int query_DEHT(DEHT *ht, const unsigned char *key, int keyLength,
		unsigned char *data, int dataMaxAllowedLength) {

	int num = 0;
	unsigned char data_buffer[MAX_WORD*MAX_RETURN_PW + 1];
	unsigned char *data_buffer_pointer[MAX_RETURN_PW + 1];
	unsigned char cur_data[MAX_WORD + 1];
	int i;
	int cur_length;
	BasicHashFunctionPtr hash = NULL;
	unsigned char hashedpassword[MAX_HASH_SIZE];

	/* exit if DEHT not found */
	if (!ht) {
		return DEHT_STATUS_FAIL;
	}

	num = mult_query_DEHT (ht, key, keyLength, data_buffer, MAX_WORD*MAX_RETURN_PW + 1, data_buffer_pointer, MAX_RETURN_PW + 1);
	/* not found at all */
	if ((num == 0) || (num == DEHT_STATUS_FAIL)){
		return DEHT_STATUS_NOT_NEEDED;
	}
	
	for (i=0;i < num; i++){
		cur_length = data_buffer_pointer[i + 1] - data_buffer_pointer[i];
		if (cur_length > dataMaxAllowedLength){ 
			continue;
		}
		memcpy(cur_data, data_buffer_pointer[i], cur_length);
		if (strcmp(ht->header.sHashName, "MD5") == 0){
			hash = MD5BasicHash;
		}
		else{
			hash = SHA1BasicHash;
		}
		cur_data[cur_length] = '\0';
		cryptHash(hash, (const char *) cur_data, hashedpassword);
		if (memcmp(hashedpassword, key, keyLength) == 0) {
			memcpy(data, cur_data, cur_length);
			return cur_length;
		}
	}
		return DEHT_STATUS_NOT_NEEDED;
}

int read_DEHT_pointers_table(DEHT *ht) {

	int status;
	int num;
	DEHT_DISK_PTR *table;
	DEHT_DISK_PTR header_size = sizeof(struct DEHTpreferences);

	/* exit if DEHT not found */
	if (!ht) {
		fprintf(stderr, "Error: Can not open DEHT>\n");
		return DEHT_STATUS_FAIL;
	}
	/* if already cached */
	if (ht->hashTableOfPointersImageInMemory)
		return DEHT_STATUS_NOT_NEEDED;

	num = ht->header.numEntriesInHashTable;
	
	/* allocate memory */
	table = (DEHT_DISK_PTR*) malloc(sizeof(DEHT_DISK_PTR) * num);
	if (!table) {
		fprintf(stderr, "Error: Not enough memory\n");
		return DEHT_STATUS_FAIL;
	}

	/* go to the beginning of the table and read it */
	status = fseek(ht->keyFP, header_size, SEEK_SET);
	if (status != 0) {
		perror(ht->sKeyFileName);
		free(table);
		return DEHT_STATUS_FAIL;
	}
	status = fread(table, sizeof(DEHT_DISK_PTR), num, ht->keyFP);
	if (status != num) {
		perror(ht->sKeyFileName);
		free(table);
		return DEHT_STATUS_FAIL;
	}

	ht->hashTableOfPointersImageInMemory = table;

	return DEHT_STATUS_SUCCESS;
}

int write_DEHT_pointers_table(DEHT *ht) {
	DEHT_DISK_PTR *table = NULL;
	DEHT_DISK_PTR header_size = sizeof(struct DEHTpreferences);
	int status, num;

	/* exit if DEHT not found */
	if (!ht) {
		return DEHT_STATUS_FAIL;
	}

	table = ht->hashTableOfPointersImageInMemory;
	num = ht->header.numEntriesInHashTable;

	/* no current table available */
	if (!table)
		return DEHT_STATUS_NOT_NEEDED;

	/* go to hash table location and write a table */
	status = fseek(ht->keyFP, header_size, SEEK_SET);
	if (status != 0) {
		perror(ht->sKeyFileName);
		return DEHT_STATUS_FAIL;
	}
	status = fwrite(table, sizeof(DEHT_DISK_PTR), num, ht->keyFP);
	if (status != num) {
		perror(ht->sKeyFileName);
		return DEHT_STATUS_FAIL;
	}
	
	/* free memory and put NULL pointer */
	if (ht->hashTableOfPointersImageInMemory) {
		free(ht->hashTableOfPointersImageInMemory);
		ht->hashTableOfPointersImageInMemory = NULL;
	}

	return DEHT_STATUS_SUCCESS;
}


int calc_DEHT_last_block_per_bucket(DEHT *ht) {
	int status;
	int i, j;
	unsigned char *block;
	DEHT_DISK_PTR pair_size_in_bytes;
	DEHT_DISK_PTR block_ptr, next_ptr_offset;
	DEHT_DISK_PTR prev_block_ptr;
	DEHT_DISK_PTR data_ptr;
	DEHT_DISK_PTR current_offset;

	pair_size_in_bytes = ht->PairSize;
	next_ptr_offset = ht->header.nPairsPerBlock * pair_size_in_bytes;

	/* exit if DEHT not found */
	if (!ht) {
		fprintf(stderr, "Error: Can not open DEHT>\n");
		return DEHT_STATUS_FAIL;
	}
	/* if already cached */
	if (ht->hashPointersForLastBlockImageInMemory)
		return DEHT_STATUS_NOT_NEEDED;

	/* read HASH table to the RAM */
	status = read_DEHT_pointers_table(ht);
	if (status == DEHT_STATUS_FAIL)
		return DEHT_STATUS_FAIL;

	/* allocate memory */
	ht->hashPointersForLastBlockImageInMemory =
			(DEHT_DISK_PTR*) calloc(sizeof(DEHT_DISK_PTR), ht->header.numEntriesInHashTable);
	if (!(ht->hashPointersForLastBlockImageInMemory)) {
		fprintf(stderr, "Error: not enough memory\n");
		return DEHT_STATUS_FAIL;
	}
	ht->anLastBlockSize = (int*) calloc(sizeof(int), ht->header.numEntriesInHashTable);
	if (!(ht->anLastBlockSize)) {
		fprintf(stderr, "Error: not enough memory\n");
		free(ht->hashPointersForLastBlockImageInMemory);
		return DEHT_STATUS_FAIL;
	}

	/* fill in the table */
	for(i=0; i < ht->header.numEntriesInHashTable; i++) {
		if(ht->hashTableOfPointersImageInMemory[i]) {
			block_ptr = ht->hashTableOfPointersImageInMemory[i];
			/* look for the last block and update  hashPointersForLastBlockImageInMemory */
			while (block_ptr) {
				prev_block_ptr = block_ptr;
				status = fseek(ht->keyFP, block_ptr+next_ptr_offset, SEEK_SET);
				if (status != 0) {
					perror(ht->sKeyFileName);
					free(ht->hashPointersForLastBlockImageInMemory);
					free(ht->anLastBlockSize);
					return DEHT_STATUS_FAIL;
				}
				status = fread(&block_ptr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP);
				if (!status) {
					perror(ht->sKeyFileName);
					free(ht->hashPointersForLastBlockImageInMemory);
					free(ht->anLastBlockSize);
					return DEHT_STATUS_FAIL;
				}
			}
			block_ptr = prev_block_ptr;
			ht->hashPointersForLastBlockImageInMemory[i] = block_ptr;

			/* load last block and find it's size */
			block = ht->workBlock;
			status = fseek(ht->keyFP, block_ptr, SEEK_SET);
			if (status != 0) {
				perror(ht->sKeyFileName);
				free(ht->hashPointersForLastBlockImageInMemory);
				free(ht->anLastBlockSize);
				return DEHT_STATUS_FAIL;
			}
			status = fread(block, sizeof(unsigned char), next_ptr_offset, ht->keyFP);
			if (status != next_ptr_offset) {
				perror(ht->sKeyFileName);
				free(ht->hashPointersForLastBlockImageInMemory);
				free(ht->anLastBlockSize);
				return DEHT_STATUS_FAIL;
			}

			data_ptr = 1;
			j = 0;
			for (current_offset = 0; data_ptr && current_offset < next_ptr_offset;
					current_offset += pair_size_in_bytes, j++) {
				memcpy(&data_ptr, block+current_offset+ht->header.nBytesPerValidationKey, sizeof(DEHT_DISK_PTR));
			}
			if (current_offset == next_ptr_offset) {
				ht->anLastBlockSize[i] = ht->header.nPairsPerBlock - 1;
			} else {
				ht->anLastBlockSize[i] = j-2;
			}
		}
	}

	return DEHT_STATUS_SUCCESS;
}


void lock_DEHT_files(DEHT *ht) {
	if (ht) {
		if (ht->workBlock)
			free(ht->workBlock);
		if (ht->hashTableOfPointersImageInMemory)
			write_DEHT_pointers_table(ht);
		if (ht->hashPointersForLastBlockImageInMemory)
			free(ht->hashPointersForLastBlockImageInMemory);
		if (ht->keyFP)
			fclose(ht->keyFP);
		if (ht->dataFP)
			fclose(ht->dataFP);
		free(ht);
	}
}

int read_DEHT_Seed(DEHT *ht, void *seed,int seedLength){

	int status;
	int file_length;

	/* check and open stream */
	ht->seedFP = fopen(ht->sSeedFileName, "rb");
	if (!ht->seedFP) {
		perror(ht->sSeedFileName);
		return DEHT_STATUS_FAIL;
	}	
	
	/* get file size */
	status = fseek(ht->seedFP, 0, SEEK_END);
	if (status != 0) { 
		perror(ht->sSeedFileName);
		fclose(ht->seedFP);
		return DEHT_STATUS_FAIL;
	}
	file_length = ftell(ht->seedFP);
	if (file_length > seedLength*sizeof(LONG_INDEX_PROJ)) {
		perror(ht->sSeedFileName);
		fprintf(stderr, "Error: Buffer is not big enough to include %s file\n", ht->sSeedFileName);
		fclose(ht->seedFP);
		return DEHT_STATUS_FAIL;
	}
	
	/* read a seed file to buffer */
	status = fseek(ht->seedFP, 0, SEEK_SET);
	if (status != 0) { 
		perror(ht->sSeedFileName);
		fclose(ht->seedFP);
		return DEHT_STATUS_FAIL;
	}

	status = fread(seed, sizeof(LONG_INDEX_PROJ), file_length, ht->seedFP);
	if (status != file_length/sizeof(LONG_INDEX_PROJ)) {
		perror(ht->sSeedFileName);
		fclose(ht->seedFP);
		return DEHT_STATUS_FAIL;
	}
	
	fclose(ht->seedFP);
	return DEHT_STATUS_SUCCESS;
}

int write_DEHT_Seed(DEHT *ht,const void *seed,int seedLength){
	
	int status;
	
	/* open stream */
	ht->seedFP = fopen(ht->sSeedFileName, "w+b");
	if (!ht->seedFP) {
		perror(ht->sSeedFileName);
		return DEHT_STATUS_FAIL;
	}
	status = fwrite(seed, sizeof(LONG_INDEX_PROJ), seedLength, ht->seedFP);
	if (!status) {
		perror(ht->sSeedFileName);
		return DEHT_STATUS_FAIL;
	}

	fclose(ht->seedFP);
	return DEHT_STATUS_SUCCESS;
}

void erase_DEHT_file(DEHT *ht){
	FILE * temp;
	if (ht) {
		if (ht->workBlock)
			free(ht->workBlock);
		if (ht->hashTableOfPointersImageInMemory)
			write_DEHT_pointers_table(ht);
		if (ht->hashPointersForLastBlockImageInMemory)
			free(ht->hashPointersForLastBlockImageInMemory);
		if (ht->keyFP)
			fclose(ht->keyFP);
		if (ht->dataFP)
			fclose(ht->dataFP);
		if ((temp = fopen(ht->sKeyFileName, "rb"))) {
			fclose(temp);
			remove(ht->sKeyFileName);
		}
		if ((temp = fopen(ht->sDataFileName, "rb"))) {
			fclose(temp);
			remove(ht->sDataFileName);
		}
		if ((temp = fopen(ht->sSeedFileName, "rb"))) {
			fclose(temp);
			remove(ht->sSeedFileName);
		}
		free(ht);
	}
}






